import 'dart:async';
import 'dart:convert';
import 'dart:io';

/// 设备发现服务
/// @Author CimZzz
/// Since Elec-Communication-Protocol 3.0
/// 监听组播、单播端口，实现对局域网内设备信息的
/// 发布与搜索

/// 设备发布组播地址
//const _kDeviceDispatchAddress = '192.168.43.255';

/// 设备发布组播端口
const _kDeviceDispatchPort = 10086;

/// 设备发布单播接收端口
//const _kDeviceRecvPort = 10087;

/// 设备过期时间，单位 1/s，目前 12 秒
const _kDeviceExpire = 12;

/// 设备发现回调接口
mixin DeviceFoundInterface {
	/// 服务已关闭
	void onServiceClosed();

	/// 发现新设备
	void onFoundDevice(Device device);
	
	/// 设备信息更新
	void onDeviceUpdate(Device device);
	
	/// 丢失设备
	void onMissDevice(Device device);
}

/// 设备数据类
class Device {
	Device(this.deviceName, this.boxSn, this.mac, this.ip, this.forward, this.model, this.netType, this.scheduleId, this.scheduleName, this.softVersion);
	// 0 - 表示热点
	// 1 - 表示 Wifi
	// 2 - 表示有线
	// 3 - 表示未知
	// 4 - 表示离线
	static const int NetType_Ap = 0;
	static const int NetType_Wifi = 1;
	static const int NetType_Eth = 2;
	static const int NetType_Unknown = 3;
	static const int NetType_Offline = 4;

	// 设备名
	String deviceName;
	// boxSn
	String boxSn;
	// mac 地址
	String mac;
	// ip
	String ip;
	// 绑定的账户
	String forward;
	// 型号
	String model;
	// 网络状态
	int netType;
	// 正在播放的计划 id
	String scheduleId;
	// 正在播放的计划名
	String scheduleName;
	// 设备 OTA 版本号
	String softVersion;
	// 设备上一次连接所使用的网络
	String apUserName;
	// 设备上一次连接所使用的密码
	String apPasswd;
	// 过期计数
	var _expireCount = 0;
	
	@override
	bool operator ==(other) {
		return other is Device &&
			deviceName == other.deviceName &&
			boxSn == other.boxSn &&
			mac == other.mac &&
			ip == other.ip &&
			forward == other.forward &&
			model == other.model &&
			netType == other.netType &&
			scheduleId == other.scheduleId &&
			scheduleName == other.scheduleName;
	}
	
	@override
	String toString() {
		return 'Device(deviceName=$deviceName, boxSn=$boxSn, mac=$mac, ip=$ip, forward=$forward, model=$model, netType=$netType, scheduleId=$scheduleId, scheduleName=$scheduleName)';
	}
}

/// 设备捕获器
class _DeviceCatcher {
    _DeviceCatcher(this.completer, this.expectBoxSnMatch);
	final Completer<Device> completer;
	final bool Function(String boxSn) expectBoxSnMatch;
}

/// 设备发现服务
/// Author CimZzz
/// Since Elec-Communication-Protocol Alpha
class DeviceFoundService {
	DeviceFoundService(this._interface, this._needForward);
	
	/// 设备发现回调接口
	DeviceFoundInterface _interface;

	/// Forward 匹配
	final String _needForward;
	
	/// 单播 Socket
	RawDatagramSocket _monoSocket;
	
	/// 设备检查定时器
	Timer _expireTimer;
	
	/// 设备表
	Map<String, Device> _deviceMap;

	/// 单播发送检查次数
	var checkCount = 0;

	/// 设备捕获器
	Set<_DeviceCatcher> _deviceCatcher;

	/// 检查服务是否成功启动
	bool get _checkServiceStarted =>
			_monoSocket != null;

	
	/// 启动设备发现服务
	Future<void> start() async {
		if (!_checkServiceStarted) {
			try {
				_monoSocket = await RawDatagramSocket.bind('0.0.0.0', _kDeviceDispatchPort);

				/// 单播接收
				_monoSocket.listen((event) async {
					switch (event) {
					/// 收到单播
						case RawSocketEvent.read:
							await _datagramReceive(_monoSocket);
							break;
					}
				}, onError: (e) {
					print('testaabc: monoSocket Error: $e');
					close();
				});

				/// 启动设备过期检查定时器
				_expireTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
					_checkDeviceExpire();
				});
				print('设备发现服务启动成功');
			}
			catch(e) {
				close();
				print('设备发现服务发生异常: $e');
				rethrow;
			}

		}
		return;
	}
	
	/// 发布搜索指令
	/// 考虑热点模式问题，此方法暂时无效
	void query() {
//		if(_checkServiceStarted) {
//			try {
//				_monoSocket.send(utf8.encode(json.encode({
//					'search': _kDeviceRecvPort
//				})), InternetAddress(_kDeviceDispatchAddress), _kDeviceDispatchPort);
//			}
//			catch(ignored) {
//				// 单播发送失败
//			}
//		}
	}

	/// 清空当前已发现所有设备
	void clearDevices() {
		if(_checkServiceStarted) {
			_deviceMap?.clear();
			_deviceMap = null;
		}
	}

	/// 获取当前已发现设备总数
	int getCurrentDevicesCount() {
		return _deviceMap?.length ?? 0;
	}

	/// 获取当前已发现的设备列表
	List<Device> getCurrentDevices() {
		return _deviceMap?.values?.toList();
	}

	/// 尝试获取设备信息
	/// 通过 expectBoxSnMatch 可以针对某一 sn 进行捕获
	/// 如果 expectBoxSnMatch 为 null 则表示捕获任意设备
	Future<Device> tryCatchDevice({bool Function(String boxSn) expectBoxSnMatch, Duration timeOut}) {
		if(!_checkServiceStarted) {
			return Future.value(null);
		}

		final completer = Completer<Device>();
		final dispatcher = _DeviceCatcher(completer, expectBoxSnMatch);
		_deviceCatcher ??= {};
		_deviceCatcher.add(dispatcher);

		final returnCompleter = Completer<Device>();

		Stream<Device> stream;

		if(timeOut == null) {
			stream = dispatcher.completer.future.asStream();
		}
		else {
			stream = dispatcher.completer.future.timeout(timeOut).asStream();
		}

		stream.listen((event) {
			if(!returnCompleter.isCompleted) {
				returnCompleter.complete(event);
			}
			_deviceCatcher?.remove(dispatcher);
		}, onError: (e) {
			if(!returnCompleter.isCompleted) {
				returnCompleter.complete(null);
			}
			_deviceCatcher?.remove(dispatcher);
		});


		return returnCompleter.future;
	}
	
	/// 关闭设备发现服务及回收相关所有资源
	void close() {
		final catcherSet = _deviceCatcher;
		_deviceCatcher?.clear();
		_deviceCatcher = null;
		catcherSet?.forEach((catcher) {
			catcher.completer.complete(null);
		});
		_interface?.onServiceClosed();
		_interface = null;
		_monoSocket?.close();
		_monoSocket = null;
		_expireTimer?.cancel();
		_expireTimer = null;
		_deviceMap?.clear();
		_deviceMap = null;
	}
	
	/// 收到 UDP 报文并进行处理
	void _datagramReceive(RawDatagramSocket socket) async {
		if (socket == null) {
			return;
		}
		
		try {
			final datagram = socket.receive();
			final device = await _processDatagram(datagram);
			if (device != null) {
				// 只有 Forward 为空或者 forward 相同的设备才会被发现
				// * 暂时去掉该逻辑
//				if(device.forward == null || _needForward == device.forward || _needForward == null) {
					_updateDeviceMap(device);
//				}
			}
		}
		catch (e) {
			return;
		}
	}
	
	/// 处理报文数据，转化为 Device
	Future<Device> _processDatagram(Datagram datagram) async {
		try {
			final map = json.decode(utf8.decode(datagram.data));
			final deviceName = map['deviceName'] as String;
			final boxSn = map['boxSn'] as String;
			final mac = map['mac'] as String;
			final ip = map['ip'] as String;
			final forward = map['forward'] as String;
			final model = map['model'] as String;
			final netType = map['netType'] as int;
			final scheduleId = map['scheduleId'] as String;
			final scheduleName = map['scheduleName'] as String;
			final softVersion = map['softVersion'] as String;
			if (boxSn != null && mac != null && ip != null && netType != null) {
				return Device(deviceName, boxSn, mac, ip, forward, model, netType, scheduleId, scheduleName, softVersion);
			}
		}
		catch (e) {
			return null;
		}
		return null;
	}
	
	/// 更新数据过期时间
	void _updateDeviceMap(Device device) {
		_deviceMap ??= {};
		final oldDevice = _deviceMap[device.boxSn];
		if (oldDevice != null) {
			if (oldDevice == device) {
				oldDevice._expireCount = 0;
			}
			else {
				// 同时迁移附加信息
				device.apUserName = oldDevice.apUserName;
				device.apPasswd = oldDevice.apPasswd;
				_interface?.onDeviceUpdate(device);
				_deviceMap[device.boxSn] = device;
				device._expireCount = 0;
			}
		}
		else {
			_interface?.onFoundDevice(device);
			_deviceMap[device.boxSn] = device;
			device._expireCount = 0;
		}
		// 检查 catch 表
		Set<_DeviceCatcher> _catchSet;
		_deviceCatcher?.forEach((catcher) {
			if(catcher.expectBoxSnMatch.call(device.boxSn)) {
				// 表示捕获到了
				_catchSet ??= {};
				_catchSet.add(catcher);
			}
		});

		_catchSet?.forEach((catcher) {
			if(!catcher.completer.isCompleted) {
				catcher.completer.complete(device);
			}
		});
	}
	
	/// 检查设备是否过期
	void _checkDeviceExpire() {
//		checkCount ++;
//		if(checkCount >= 2) {
//			checkCount = 0;
//			query();
//		}
		final expiredDeviceList = <Device>[];
		_deviceMap?.forEach((key, value) {
			value._expireCount ++;
			if (value._expireCount >= _kDeviceExpire) {
				expiredDeviceList.add(value);
			}
		});
		expiredDeviceList.forEach((device) {
			_interface?.onMissDevice(device);
			_deviceMap.remove(device.boxSn);
		});
		if(_deviceMap?.isEmpty == true) {
			_deviceMap = null;
		}
	}
}